from typing import Dict, Any, List, Optional
from datetime import datetime
from bson import ObjectId
from pymongo.collection import Collection
from utils.logger import get_logger
from utils.exceptions import (
    ContractNotFoundException,
    ContractDeploymentException,
    ValidationException
)

logger = get_logger(__name__)

class SmartContractManager:
    """智能合约管理服务 (SCM)
    
    负责:
    1. 智能合约的部署和管理
    2. 合约生命周期管理
    3. 合约模板管理
    4. 合约权限控制
    5. 合约状态监控
    """
    
    def __init__(self, mongo_db):
        self.db = mongo_db
        self.contracts_collection: Collection = mongo_db.contracts
        self.templates_collection: Collection = mongo_db.contract_templates
        self.deployments_collection: Collection = mongo_db.contract_deployments
        
        # 创建索引
        self._create_indexes()
    
    def _create_indexes(self):
        """创建数据库索引"""
        try:
            # 合约集合索引
            self.contracts_collection.create_index([("address", 1), ("chain_id", 1)], unique=True)
            self.contracts_collection.create_index("name")
            self.contracts_collection.create_index("owner")
            self.contracts_collection.create_index("status")
            self.contracts_collection.create_index("created_at")
            
            # 模板集合索引
            self.templates_collection.create_index("name", unique=True)
            self.templates_collection.create_index("category")
            self.templates_collection.create_index("version")
            
            # 部署记录集合索引
            self.deployments_collection.create_index("contract_id")
            self.deployments_collection.create_index("tx_hash")
            self.deployments_collection.create_index("deployer")
            self.deployments_collection.create_index("deployed_at")
            
        except Exception as e:
            logger.warning(f"Failed to create indexes: {str(e)}")
    
    def register_contract(self, contract_data: Dict[str, Any]) -> str:
        """注册智能合约"""
        try:
            # 验证必需字段
            required_fields = ['name', 'address', 'chain_id', 'abi', 'bytecode']
            for field in required_fields:
                if field not in contract_data:
                    raise ValidationException(f"Missing required field: {field}")
            
            # 检查合约是否已存在
            existing = self.contracts_collection.find_one({
                'address': contract_data['address'],
                'chain_id': contract_data['chain_id']
            })
            
            if existing:
                raise ValidationException(f"Contract already exists at address {contract_data['address']}")
            
            # 准备合约文档
            contract_doc = {
                'name': contract_data['name'],
                'address': contract_data['address'],
                'chain_id': contract_data['chain_id'],
                'abi': contract_data['abi'],
                'bytecode': contract_data['bytecode'],
                'source_code': contract_data.get('source_code'),
                'compiler_version': contract_data.get('compiler_version'),
                'owner': contract_data.get('owner'),
                'description': contract_data.get('description'),
                'tags': contract_data.get('tags', []),
                'status': 'active',
                'created_at': datetime.utcnow(),
                'updated_at': datetime.utcnow(),
                'deployment_tx_hash': contract_data.get('deployment_tx_hash'),
                'constructor_params': contract_data.get('constructor_params'),
                'metadata': contract_data.get('metadata', {})
            }
            
            # 插入合约记录
            result = self.contracts_collection.insert_one(contract_doc)
            contract_id = str(result.inserted_id)
            
            logger.info(f"Contract registered successfully: {contract_id}")
            return contract_id
            
        except Exception as e:
            logger.error(f"Failed to register contract: {str(e)}")
            raise ContractDeploymentException(f"Contract registration failed: {str(e)}")
    
    def get_contract(self, contract_id: str = None, address: str = None, chain_id: str = None) -> Dict[str, Any]:
        """获取合约信息"""
        try:
            query = {}
            
            if contract_id:
                query['_id'] = ObjectId(contract_id)
            elif address and chain_id:
                query = {'address': address, 'chain_id': chain_id}
            else:
                raise ValidationException("Must provide either contract_id or (address and chain_id)")
            
            contract = self.contracts_collection.find_one(query)
            if not contract:
                raise ContractNotFoundException("Contract not found")
            
            # 转换ObjectId为字符串
            contract['_id'] = str(contract['_id'])
            
            return contract
            
        except Exception as e:
            if isinstance(e, (ContractNotFoundException, ValidationException)):
                raise
            logger.error(f"Failed to get contract: {str(e)}")
            raise ContractNotFoundException(f"Failed to retrieve contract: {str(e)}")
    
    def list_contracts(self, chain_id: str = None, owner: str = None, status: str = None, 
                      page: int = 1, page_size: int = 20) -> Dict[str, Any]:
        """列出合约"""
        try:
            query = {}
            
            if chain_id:
                query['chain_id'] = chain_id
            if owner:
                query['owner'] = owner
            if status:
                query['status'] = status
            
            # 计算跳过的文档数
            skip = (page - 1) * page_size
            
            # 查询合约
            cursor = self.contracts_collection.find(query).sort('created_at', -1).skip(skip).limit(page_size)
            contracts = list(cursor)
            
            # 转换ObjectId为字符串
            for contract in contracts:
                contract['_id'] = str(contract['_id'])
            
            # 获取总数
            total_count = self.contracts_collection.count_documents(query)
            
            return {
                'contracts': contracts,
                'total_count': total_count,
                'page': page,
                'page_size': page_size,
                'total_pages': (total_count + page_size - 1) // page_size
            }
            
        except Exception as e:
            logger.error(f"Failed to list contracts: {str(e)}")
            raise
    
    def update_contract(self, contract_id: str, update_data: Dict[str, Any]) -> bool:
        """更新合约信息"""
        try:
            # 不允许更新的字段
            protected_fields = ['_id', 'address', 'chain_id', 'created_at', 'deployment_tx_hash']
            for field in protected_fields:
                if field in update_data:
                    del update_data[field]
            
            # 添加更新时间
            update_data['updated_at'] = datetime.utcnow()
            
            result = self.contracts_collection.update_one(
                {'_id': ObjectId(contract_id)},
                {'$set': update_data}
            )
            
            if result.matched_count == 0:
                raise ContractNotFoundException("Contract not found")
            
            logger.info(f"Contract updated successfully: {contract_id}")
            return True
            
        except Exception as e:
            if isinstance(e, ContractNotFoundException):
                raise
            logger.error(f"Failed to update contract: {str(e)}")
            raise
    
    def deactivate_contract(self, contract_id: str, reason: str = None) -> bool:
        """停用合约"""
        try:
            update_data = {
                'status': 'inactive',
                'deactivated_at': datetime.utcnow(),
                'updated_at': datetime.utcnow()
            }
            
            if reason:
                update_data['deactivation_reason'] = reason
            
            result = self.contracts_collection.update_one(
                {'_id': ObjectId(contract_id)},
                {'$set': update_data}
            )
            
            if result.matched_count == 0:
                raise ContractNotFoundException("Contract not found")
            
            logger.info(f"Contract deactivated: {contract_id}")
            return True
            
        except Exception as e:
            if isinstance(e, ContractNotFoundException):
                raise
            logger.error(f"Failed to deactivate contract: {str(e)}")
            raise
    
    def create_template(self, template_data: Dict[str, Any]) -> str:
        """创建合约模板"""
        try:
            # 验证必需字段
            required_fields = ['name', 'source_code', 'abi', 'category']
            for field in required_fields:
                if field not in template_data:
                    raise ValidationException(f"Missing required field: {field}")
            
            # 检查模板名称是否已存在
            existing = self.templates_collection.find_one({'name': template_data['name']})
            if existing:
                raise ValidationException(f"Template with name '{template_data['name']}' already exists")
            
            # 准备模板文档
            template_doc = {
                'name': template_data['name'],
                'description': template_data.get('description'),
                'category': template_data['category'],
                'version': template_data.get('version', '1.0.0'),
                'source_code': template_data['source_code'],
                'abi': template_data['abi'],
                'bytecode': template_data.get('bytecode'),
                'compiler_version': template_data.get('compiler_version'),
                'constructor_params': template_data.get('constructor_params', []),
                'tags': template_data.get('tags', []),
                'author': template_data.get('author'),
                'license': template_data.get('license'),
                'documentation': template_data.get('documentation'),
                'created_at': datetime.utcnow(),
                'updated_at': datetime.utcnow()
            }
            
            result = self.templates_collection.insert_one(template_doc)
            template_id = str(result.inserted_id)
            
            logger.info(f"Contract template created: {template_id}")
            return template_id
            
        except Exception as e:
            logger.error(f"Failed to create contract template: {str(e)}")
            raise
    
    def get_template(self, template_id: str) -> Dict[str, Any]:
        """获取合约模板"""
        try:
            template = self.templates_collection.find_one({'_id': ObjectId(template_id)})
            if not template:
                raise ContractNotFoundException("Template not found")
            
            template['_id'] = str(template['_id'])
            return template
            
        except Exception as e:
            if isinstance(e, ContractNotFoundException):
                raise
            logger.error(f"Failed to get template: {str(e)}")
            raise
    
    def list_templates(self, category: str = None, page: int = 1, page_size: int = 20) -> Dict[str, Any]:
        """列出合约模板"""
        try:
            query = {}
            if category:
                query['category'] = category
            
            skip = (page - 1) * page_size
            
            cursor = self.templates_collection.find(query).sort('created_at', -1).skip(skip).limit(page_size)
            templates = list(cursor)
            
            for template in templates:
                template['_id'] = str(template['_id'])
            
            total_count = self.templates_collection.count_documents(query)
            
            return {
                'templates': templates,
                'total_count': total_count,
                'page': page,
                'page_size': page_size,
                'total_pages': (total_count + page_size - 1) // page_size
            }
            
        except Exception as e:
            logger.error(f"Failed to list templates: {str(e)}")
            raise
    
    def record_deployment(self, deployment_data: Dict[str, Any]) -> str:
        """记录合约部署"""
        try:
            deployment_doc = {
                'contract_id': deployment_data.get('contract_id'),
                'template_id': deployment_data.get('template_id'),
                'chain_id': deployment_data['chain_id'],
                'deployer': deployment_data['deployer'],
                'tx_hash': deployment_data['tx_hash'],
                'contract_address': deployment_data.get('contract_address'),
                'constructor_params': deployment_data.get('constructor_params'),
                'gas_used': deployment_data.get('gas_used'),
                'deployment_cost': deployment_data.get('deployment_cost'),
                'status': deployment_data.get('status', 'pending'),
                'deployed_at': datetime.utcnow(),
                'metadata': deployment_data.get('metadata', {})
            }
            
            result = self.deployments_collection.insert_one(deployment_doc)
            deployment_id = str(result.inserted_id)
            
            logger.info(f"Deployment recorded: {deployment_id}")
            return deployment_id
            
        except Exception as e:
            logger.error(f"Failed to record deployment: {str(e)}")
            raise
    
    def get_contract_statistics(self, chain_id: str = None) -> Dict[str, Any]:
        """获取合约统计信息"""
        try:
            pipeline = []
            
            # 添加匹配条件
            if chain_id:
                pipeline.append({'$match': {'chain_id': chain_id}})
            
            # 按状态分组统计
            pipeline.extend([
                {
                    '$group': {
                        '_id': '$status',
                        'count': {'$sum': 1}
                    }
                },
                {
                    '$group': {
                        '_id': None,
                        'status_counts': {
                            '$push': {
                                'status': '$_id',
                                'count': '$count'
                            }
                        },
                        'total_count': {'$sum': '$count'}
                    }
                }
            ])
            
            result = list(self.contracts_collection.aggregate(pipeline))
            
            if result:
                stats = result[0]
                status_counts = {item['status']: item['count'] for item in stats['status_counts']}
                return {
                    'total_contracts': stats['total_count'],
                    'status_distribution': status_counts
                }
            else:
                return {
                    'total_contracts': 0,
                    'status_distribution': {}
                }
                
        except Exception as e:
            logger.error(f"Failed to get contract statistics: {str(e)}")
            raise